#include <float.h>
#include "object.h"
#include "opengl.h"
#include "unistate.h"
#include "logger.h"

using namespace goatgfx;

static void destroy_all_rec(XFormNode *node);
static void get_all_meshes_rec(XFormNode *node, std::list<Mesh*> *reslist);

DrawMode Object::draw_mode = DRAW_DEFAULT;

Object::Object()
{
	mesh = 0;
}

void Object::destroy_all()
{
	destroy_all_rec(this);
}

static void destroy_all_rec(XFormNode *node)
{
	if(!node) {
		return;
	}

	for(int i=0; i<node->get_children_count(); i++) {
		destroy_all_rec(node->get_child(i));
	}
	delete node;
}

void Object::set_mesh(Mesh *m)
{
	mesh = m;
}

Mesh *Object::get_mesh()
{
	return mesh;
}

const Mesh *Object::get_mesh() const
{
	return mesh;
}

std::list<Mesh*> Object::get_all_meshes()
{
	std::list<Mesh*> meshes;
	get_all_meshes_rec(this, &meshes);
	return meshes;
}

std::list<const Mesh*> Object::get_all_meshes() const
{
	std::list<const Mesh*> meshes;
	get_all_meshes_rec((Object*)this, (std::list<Mesh*>*)&meshes);
	return meshes;
}

static void get_all_meshes_rec(XFormNode *node, std::list<Mesh*> *reslist)
{
	if(!node) {
		return;
	}

	Object *obj = dynamic_cast<Object*>(node);
	if(obj) {
		Mesh *mesh = obj->get_mesh();
		if(mesh) {
			reslist->push_back(mesh);
		}
	}

	for(int i=0; i<node->get_children_count(); i++) {
		get_all_meshes_rec(node->get_child(i), reslist);
	}
}

AABox Object::get_aabbox() const
{
	AABox box;

	if(mesh) {
		box = mesh->get_aabbox();
	} else {
		box.min = Vector3(FLT_MAX, FLT_MAX, FLT_MAX);
		box.max = -box.min;
	}

	int num_children = get_children_count();
	for(int i=0; i<num_children; i++) {
		const Object *obj = dynamic_cast<const Object*>(get_child(i));
		if(obj) {
			AABox child_box = obj->get_aabbox();
			box.set_union(&box, &child_box);
		}
	}
	return box;
}

Sphere Object::get_bsphere() const
{
	Sphere sph;
	bool valid = false;

	if(mesh) {
		sph = mesh->get_bsphere();
		valid = true;
	} else {
		sph.radius = 0.0;
	}

	int num_children = get_children_count();
	for(int i=0; i<num_children; i++) {
		const Object *obj = dynamic_cast<const Object*>(get_child(i));
		if(obj) {
			Sphere child_sph = obj->get_bsphere();
			if(valid) {
				sph.set_union(&sph, &child_sph);
			} else {
				sph = child_sph;
				valid = true;
			}
		}
	}

	return sph;
}

/*static const char *attr_name[] = {
	"attr_vertex",
	"attr_normal",
	"attr_tangent",
	"attr_texcoord",
	"attr_boneweights",
	"attr_boneidx"
};*/

void Object::draw(long msec) const
{
	Matrix4x4 xform;
	get_xform(msec, &xform);

	set_world_matrix(xform);

	if(mesh) {
		/*unsigned int prog = sdrprog;

		if(mesh->get_bones_count() > 0) {
			prog = sdrprog_skin;
		}

		glUseProgram(prog);
		// get all the attribute locations
		for(int i=0; i<NUM_MESH_ATTR; i++) {
			int loc = glGetAttribLocation(prog, attr_name[i]);
			if(loc != -1) {
				Mesh::set_attrib_location(i, loc);
			}
		}

		setup_bones(msec);

		glUseProgram(prog);*/

		material.setup();
		setup_unistate();	// set all state uniforms

		switch(Object::draw_mode) {
		case DRAW_WIREFRAME:
			mesh->draw_wire();
			break;

		case DRAW_VERTICES:
			mesh->draw_vertices();
			break;

		case DRAW_DEFAULT:
		default:
			mesh->draw();
		}
	}

	int num_children = get_children_count();
	for(int i=0; i<num_children; i++) {
		const Object *obj = dynamic_cast<const Object*>(get_child(i));
		if(obj) {
			obj->draw(msec);
		}
	}
}


bool Object::intersect(const Ray &inray, HitPoint *hit) const
{
	Ray ray = inray;
	Matrix4x4 xform, inv_xform;
	get_xform(ray.time, &xform/*, &inv_xform*/);
	ray.transform(xform.inverse());	// TODO find out what's wrong with get_xform's inv_xform and use that

	HitPoint nearest_hit;
	nearest_hit.dist = FLT_MAX;
	nearest_hit.obj = 0;

	if(mesh) {
		if(mesh->intersect(ray, hit ? &nearest_hit : 0)) {
			if(!hit) {
				return true;
			}

			if(Mesh::get_intersect_mode() & ISECT_FACE) {
				Triangle *face = (Triangle*)nearest_hit.obj;
				face->transform(xform);
			} else if(Mesh::get_intersect_mode() & ISECT_VERTICES) {
				Vector3 *v = (Vector3*)nearest_hit.obj;
				v->transform(xform);
			} else {
				nearest_hit.obj = this;
			}
		}
	}

	int num_children = get_children_count();
	for(int i=0; i<num_children; i++) {
		const Object *obj = dynamic_cast<const Object*>(get_child(i));

		HitPoint chit;
		if(obj && obj->intersect(inray, hit ? &chit : 0)) {
			if(!hit) {
				return true;
			}

			if(chit.dist < nearest_hit.dist) {
				nearest_hit = chit;
			}
		}
	}

	if(nearest_hit.obj) {
		if(hit) {
			*hit = nearest_hit;
		}
		return true;
	}
	return false;
}


bool Object::setup_bones(long msec) const
{
	int num_bones;
	if(!mesh || !(num_bones = mesh->get_bones_count())) {
		return false;
	}

	/*char uniname[32];

	for(int i=0; i<num_bones; i++) {
		const XFormNode *bone = mesh->get_bone(i);

		Matrix4x4 xform;
		bone->get_xform(msec, &xform);

		xform = xform * bone->get_bone_matrix();

		sprintf(uniname, "bone_xform[%d]", i);
		int loc = glGetUniformLocation(sdrprog_skin, uniname);
		if(loc == -1) {
			return false;
		}
		glUniformMatrix4fv(loc, 1, GL_TRUE, xform[0]);
	}*/
	return true;
}

DrawMode Object::set_draw_mode(DrawMode mode)
{
	DrawMode prev = Object::draw_mode;
	Object::draw_mode = mode;
	return prev;
}
